package ua.pp.parkee.t3;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.*;
import java.util.concurrent.locks.ReentrantLock;

/**
 * Created by Pavlo_Klivak on 8/18/2014.
 */
public class ConnectionPoolImpl implements ConnectionPool {
    private static final int PERMITS = 10;
    private final Semaphore semaphore;
    private final BlockingQueue<PooledConnection> idle;
    private final List<PooledConnection> busy;
    private final ReentrantLock reentrantLock;
    private static final long WAITING_TIMEOUT = 5000;
    private static final long IDLE_TIMEOUT = 10000;
    private static final String JDBC_DRIVER = "com.mysql.jdbc.Driver";
    private final PoolCleaner poolCleaner;
    private Boolean terminated;


    public ConnectionPoolImpl(String url, String user, String password) throws ClassNotFoundException, SQLException {

        this.semaphore = new Semaphore(PERMITS);

        idle = new ArrayBlockingQueue<>(PERMITS);
        busy = new ArrayList<>(PERMITS);

        reentrantLock = new ReentrantLock();

        Class.forName(JDBC_DRIVER);
        Connection conn;


        for (int i = 0; i < PERMITS; i++) {
            conn = DriverManager.getConnection(url, user, password);
            idle.add(new PooledConnection(conn, this));
        }

        poolCleaner = new PoolCleaner();
        poolCleaner.start();
    }

    @Override
    public Connection checkout() throws TimeoutException {

        if (terminated) throw new IllegalStateException();

        reentrantLock.lock();
        try {
            semaphore.tryAcquire(WAITING_TIMEOUT, TimeUnit.MILLISECONDS);
            PooledConnection pooledConnection = idle.poll(WAITING_TIMEOUT, TimeUnit.MILLISECONDS);
            pooledConnection.setTimestamp(System.currentTimeMillis());
            busy.add(pooledConnection);
            return pooledConnection;
        } catch (InterruptedException e) {
            throw new TimeoutException();
        } finally {
            reentrantLock.unlock();
        }
    }

    @Override
    public void checkin(PooledConnection connection) {

        if(connection == null)
            return;

        reentrantLock.lock();
        try {
            if (busy.contains(connection)) {

                busy.remove(connection);
                try {
                    idle.put(connection);
                } catch (InterruptedException e) {
                    //Ignore
                }
                semaphore.release();
            }
        } finally {
            reentrantLock.unlock();
        }

    }

    public void terminate() throws SQLException {
        reentrantLock.lock();
        try {

            poolCleaner.interrupt();
            for (PooledConnection pooledConnection : busy) {
                pooledConnection.close();
            }
            terminated = true;

        } finally {
            reentrantLock.unlock();
        }
    }

    public boolean isTerminated() {
        return terminated;
    }

    private class PoolCleaner extends Thread {

        public PoolCleaner() {
            setDaemon(true);
        }

        @Override
        public void run() {
            while (!this.isInterrupted()) {

                for (PooledConnection pooledConnection : busy) {
                    long now = System.currentTimeMillis();
                    long timeCreated = pooledConnection.getTimestamp();

                    if (now - timeCreated > IDLE_TIMEOUT) {
                        try {
                            pooledConnection.close();
                        } catch (SQLException e) {
                            //Ignore
                        }
                    }
                }

                try {
                    sleep(3000);
                } catch (InterruptedException e) {
                    //Ignore
                }
            }
        }
    }
}
